/*
 * Copyright (C) 2005-2008 Dmitri Maximovich <maxim@md.pp.ru>
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 */

// For JS namespaces technique see:
// http://blogger.ziesemer.com/2007/10/respecting-javascript-global-namespace.html

if(!com) var com={};
if(!com.maximdim) com.maximdim={};
if(!com.maximdim.cc) com.maximdim.cc={};
if(!com.maximdim.cc.main) com.maximdim.cc.main={};

com.maximdim.cc.main = {
  ccServers: null,
  //onpopupshowing getting triggered many times if there are nested sub popups.
  // This is the workaround. Can we do better?
  ccPopupShowing: 0,

  log: function(msg) {
    dump((new Date).getTime()/1000+": "+msg+"\n");
  },

  onLoad: function() {
	var cc = com.maximdim.cc.main;

	var refreshInterval = parseInt(com.maximdim.cc.prefs.getString("refreshInterval", 60000));
	cc.log("CC initialized. Refresh="+refreshInterval);
    cc.refreshData();
    setInterval(cc.refreshData, refreshInterval);
  },

  refreshData: function() {
	var cc = com.maximdim.cc.main;

	cc.log("*** refreshData");
    // refresh servers
	cc.ccServers = com.maximdim.cc.prefs.getServersAsArray();

    for(var i = 0; i < cc.ccServers.length; i++) {
      var server = cc.ccServers[i];
      cc.log("Server: "+server.name);

      try {
	      var request = new XMLHttpRequest();
	      request.mozBackgroundRequest = true;
	      // note syntax for closures. See http://www.kryogenix.org/code/browser/secrets-of-javascript-closures/secrets_of_javascript_closures.pdf
	      request.onload = (function(s, r) {
	    	  return function() {
	    		  cc.handleServerResponse(s, r);
                  if (cc.refreshDone()) cc.updateToolbar();
	    	  }
	      })(server, request);

	      request.onerror = (function(s, r) {
	    	  return function() {
	    		  cc.handleServerError(s, r);
                  if (cc.refreshDone()) cc.updateToolbar();
              }
	      })(server, request);

	      request.open("GET", server.url, true); // true == async
	      request.setRequestHeader("User-Agent", "Mozilla/4.0 (compatible; cc-monitor; http://www.md.pp.ru/mozilla/cc/)");
	      // bypass cache
	      request.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
	      request.send(null);
	  }
	  catch (e) {
	    var msg = "Exception sending HTTP request. Check URL"
	    server.error = msg;
	    cc.log(msg);
	    if (cc.refreshDone) cc.updateToolbar();
	  }
    }
  },

  // detect that all XMLHttpRequests returned
  // JS is single threaded so we don't need to worry about synchronization etc even so we're using Async XmlHttpRequest
  refreshDone: function() {
      var cc = com.maximdim.cc.main;

	  var servers = cc.ccServers;
  	  for(var i = 0; i < servers.length; i++) {
  		  var server = servers[i];
  		  if (server.error == null && server.projects == null) {
  			  return false;
  		  }
  	  }
  	  return true;
  },

  handleServerResponse: function(server, request) {
    var cc = com.maximdim.cc.main;

	if (request.readyState != 4 || request.status != 200) {
		var msg = "Request failure. (HTTP status: "+request.status+")";
	    cc.log(msg);
	    server.error = msg;
		return;
	}
    //cc.log("Got response from "+server.name+": "+request.responseText);
    var items = request.responseXML.getElementsByTagName("item");
	cc.log(server.name+" has "+items.length+" projects");

    /* Format of the response:
    <item>
    <title>HEAD-cmdline-jmxclient passed 10/02/2006 23:26:28</title>
    <description>Build passed</description>
    <pubDate>10/02/2006 23:26:28</pubDate>
    <link>http://builds.archive.org:8080/cruisecontrol/buildresults/HEAD-cmdline-jmxclient</link>
    </item>

	<item>
	<title>HEAD-archive-access FAILED 02/27/2007 15:46:48</title>
	<description>Build FAILED</description>
	<pubDate>02/27/2007 15:46:48</pubDate>
	<link>http://builds.archive.org:8080/cruisecontrol/buildresults/HEAD-archive-access</link>
	</item>
    */
    server.projects = new Array();
    for (var i = 0; i < items.length; i++) {
    	var item = items[i];
    	var project = new Object();
    	project.title = item.getElementsByTagName("title").item(0).firstChild.nodeValue;
    	project.description = item.getElementsByTagName("description").item(0).firstChild.nodeValue;
    	project.pubDate = item.getElementsByTagName("pubDate").item(0).firstChild.nodeValue;
    	project.link = item.getElementsByTagName("link").item(0).firstChild.nodeValue;
    	// derived attributes
    	project.passed = project.description != null && project.description.toLowerCase().indexOf('passed') >= 0;
    	var n = project.title.toLowerCase().indexOf(project.passed ? ' passed' : ' failed');
    	project.name = n > 0 ? project.title.substring(0, n) : project.title;

    	cc.log(project.name+": "+project.passed);
    	server.projects.push(project);
    }

	cc.log(server.name+" done");
  },

  handleServerError: function(server, request) {
    var cc = com.maximdim.cc.main;

    cc.log(server.name+": Request failure. (HTTP status: "+request.status+")");
    server.error = "Request failure. (HTTP status: "+request.status+")";
  },

  ///////////////////////////// Tooltip stuff
  onTooltipShowing: function() {
       var cc = com.maximdim.cc.main;

	    var tooltip = document.getElementById('cc-tooltip');
	    var box = document.createElement("vbox");
	    box.setAttribute("id", "cc-vbox");

		var grid = document.createElement("grid");
		box.appendChild(grid);

		var columns = document.createElement("columns");
		columns.appendChild(document.createElement("column")); // name
		columns.appendChild(document.createElement("column")); // date

		grid.appendChild(columns);

		var rows = document.createElement("rows");
		grid.appendChild(rows);

	    // grab reference to prevent on the fly changes
	    var servers = cc.ccServers;
	    if (servers != null) {
			for(var i = 0; i < servers.length; i++) {
				var server = servers[i];
				// show error
				if (server.error != null) {
		  			var label = document.createElement("label");
		  			label.setAttribute("value", "server '"+server.name+"': "+server.error);
		  			label.setAttribute("style", "background-color: yellow; color:red; font-weight: bold;");
		  			rows.appendChild(label);
		  			continue;
				}

				for(var j = 0; j < server.projects.length; j++) {
		  			var project = server.projects[j];

		  			var row = document.createElement("row");
		  		    row.setAttribute("style", project.passed ? "color:green;" : "color:red;");

		  			// project name
		  			var label = document.createElement("label");
		  			label.setAttribute("value", project.name);
		  			row.appendChild(label);

		  			// date
		  			label = document.createElement("label");
		  			label.setAttribute("value", project.pubDate);
		  			row.appendChild(label);

		            rows.appendChild(row);
		  		}
			}
	    }

		// show
		tooltip.appendChild(box);
	  },

	  constructHeader: function() {
		var row = document.createElement("row");
		row.setAttribute("style", "text-decoration:underline;");

		var label = document.createElement("label");
		label.setAttribute("value", "Project");
		row.appendChild(label);

		label = document.createElement("label");
		label.setAttribute("value", "Date");
		row.appendChild(label);

		return row;
	  },

	  onTooltipHiding: function() {
	    var old = document.getElementById('cc-vbox');
	    if (old != null) {
		    old.parentNode.removeChild(old);
		}
	  },

	  updateToolbar: function() {
        var cc = com.maximdim.cc.main;

	    cc.log("*** updateToolbar");
	  	var servCount = 0;
	  	var totalProjects = 0;
	  	var failedProjects = 0;

	  	var passed = 0;
	  	var failed = 0;
	  	var error = "";
	  	var servers = cc.ccServers;
	  	for(var i = 0; i < servers.length; i++) {
	  		var server = servers[i];
			// show error
			if (server.error != null) {
	  			error += "!";
				continue;
			}

	  		for(var j = 0; j < server.projects.length; j++) {
	  			var project = servers[i].projects[j];
	  			if (project.passed) {
	  				passed++;
	  			}
	  			else {
	  				failed++;
	  			}
	  		}
	  	}

	    var toolbar = document.getElementById('cc-status');
	    toolbar.setAttribute("label", "passed: "+passed+", failed: "+failed+(error.length > 0 ? " ("+error+")" : ""));
	    if (failed == 0) {
	      toolbar.setAttribute("style", "color:green;");
	    }
	    else {
	      toolbar.setAttribute("style", "color:red;");
	    }
	  },

	  onProjectsMenuShowing: function() {
	      var cc = com.maximdim.cc.main;

		  cc.ccPopupShowing++;
		  if (cc.ccPopupShowing != 1) {
			  return;
		  }

		  var menu = document.getElementById('cc-popup-projects');
		  // clear previous menu
		  while (menu.hasChildNodes()) {
			      menu.removeChild(menu.lastChild);
		    }

		    // grab reference to prevent on the fly changes
		    var servers = cc.ccServers;

			for(var i = 0; i < servers.length; i++) {
				var server = servers[i];
				// skip errors
				if (server.error != null) {
		  			continue;
				}

				// skip servers which desn't have jmxUrl defined
				if (server.jmxUrl == null || server.jmxUrl.length == 0) {
					continue;
				}

				for(var j = 0; j < server.projects.length; j++) {
					var project = server.projects[j];
				    var item = document.createElement("menu");
				    item.setAttribute("label", project.name);
                    //item.setAttribute("style", project.passed ? "color: green;" : "color: red;");

				    // add commands
                    var popup = document.createElement("menupopup");
                    popup.appendChild(cc.createActionItem("Build", project.name, server.jmxUrl, "build"));
                    popup.appendChild(cc.createActionItem("Pause", project.name, server.jmxUrl, "pause"));
                    popup.appendChild(cc.createActionItem("Resume", project.name, server.jmxUrl, "resume"));

                    item.appendChild(popup);
				    menu.appendChild(item);
				}
			}
	  },

	  createActionItem: function(label, projectName, jmxUrl, action) {
          var command = document.createElement("menuitem");
          command.setAttribute("label", label);
          command.setAttribute("oncommand", 'com.maximdim.cc.main.projectAction("'+jmxUrl+'", "'+projectName+'", "'+action+'");');
          return command;
	  },

	  onProjectsMenuHiding: function() {
           var cc = com.maximdim.cc.main;

		  	if (cc.ccPopupShowing > 0) {
		  		cc.ccPopupShowing--;
	    	}
	  },

	  projectAction: function(jmxUrl,  projectName, action) {
           var cc = com.maximdim.cc.main;

		    cc.log("Executing '"+action+"' for '"+projectName+"' on '"+jmxUrl+"'");
		    try {
		  	  var request = new XMLHttpRequest();
		  	  request.onload = function() {
		  	      try {
		  		      //alert("Request status: "+request.status);
		  		      //alert(request.responseText);

		                if (request.readyState != 4 || request.status != 200) {
		  					// status is not complete
		  					alert("Error invoking JMX action. HTTP status: "+request.status);
		  					return;
		                }

		  			  // see doc http://www.xulplanet.com/references/objref/Document.html
		  			  // we cannot use responseXML because CC JMX doesn't give us Content-type: text/xml even when using template=identity???
		  			  var xmlParser = new DOMParser();
		  		      var xmlDocument = xmlParser.parseFromString(request.responseText, 'text/xml');

		  		      // this is Node
		  		      var operationResult = xmlDocument.documentElement.firstChild;
		  			  var result = operationResult.getAttribute("result");
		  			  if (result == "success") {
		  				alert("Successfully invoked JMX operation: '"+action+"' on '"+projectName+"'");
		  			  }
		  			  else {
		  			    var errorMsg = operationResult.getAttribute("errorMsg");
		  				alert("Error invoking JMX operation: '"+action+"' on '"+projectName+"': "+errorMsg);
		  			  }
		  		   }
		  		   catch(e) {
		  		   		alert("Error processing JMX call response: "+e);
		  		   }
		  	  };
		  	  //encodeURIComponent("");
		  	  var url =  jmxUrl+"/invoke?template=identity&operation="+action+"&objectname=CruiseControl+Project%3Aname%3D"+projectName;
		  	  cc.log(url);

		  	  request.open("GET", url, true); // true == async
		  	  request.send(null);
	      }
	  	catch (e) {
	  		alert("Error "+msg+": Make sure that JMX URL is correct and pointing to the same CruiseControl server in Options dialog.");
	  		cc.log("Error "+msg+": "+e);
	  	}
	  }

};  // end of CC var

window.addEventListener("load", function(e) { com.maximdim.cc.main.onLoad(e); }, false);
